package com.packt.footballpg;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import jakarta.transaction.Transactional;

@Service
public class AlbumsService {
    private AlbumRepository albumsRepository;
    private UserRepository usersRepository;
    private PlayerRepository playersRepository;
    private CardRepository cardsRepository;

    public AlbumsService(AlbumRepository albumsRepository, UserRepository usersRepository,
            PlayerRepository playersRepository, CardRepository cardsRepository) {
        this.albumsRepository = albumsRepository;
        this.usersRepository = usersRepository;
        this.playersRepository = playersRepository;
        this.cardsRepository = cardsRepository;
    }

    public Album buyAlbum(Integer userId, String title) {
        AlbumEntity album = new AlbumEntity();
        album.setTitle(title);
        album.setExpireDate(LocalDate.now().plusYears(1));
        album.setOwner(usersRepository.findById(userId).orElseThrow());
        album = albumsRepository.save(album);
        return new Album(album.getId(), album.getTitle(), album.getOwner().getId());
    }

    public List<Card> buyCards(Integer userId, Integer count) {
        Random rnd = new Random();
        List<PlayerEntity> players = getAvailablePlayers();
        UserEntity owner = usersRepository.findById(userId).orElseThrow();
        List<CardEntity> cards = Stream.generate(() -> {
            CardEntity card = new CardEntity();
            card.setOwner(owner);
            card.setPlayer(players.get(rnd.nextInt(players.size())));
            return card;
        }).limit(count).toList();
        return cardsRepository.saveAll(cards)
                .stream()
                .map(card -> new Card(card.getId(), card.getOwner().getId(), Optional.empty(),
                        new Player(card.getPlayer().getName(), card.getPlayer().getJerseyNumber(),
                                card.getPlayer().getPosition(), card.getPlayer().getDateOfBirth())))
                .collect(Collectors.toList());
    }

    public Card addCardToAlbum(Integer cardId, Integer albumId) {
        CardEntity card = cardsRepository.findById(cardId).orElseThrow();
        AlbumEntity album = albumsRepository.findById(albumId).orElseThrow();
        card.setAlbum(album);
        card = cardsRepository.save(card);
        return new Card(card.getId(), card.getOwner().getId(), Optional.of(card.getAlbum().getId()),
                new Player(card.getPlayer().getName(), card.getPlayer().getJerseyNumber(),
                        card.getPlayer().getPosition(), card.getPlayer().getDateOfBirth()));

    }

    /*
     * Take all non used cards of the user and assign to the albums of the user
     */
    public List<Card> useAllCardAvailable(Integer userId) {
        // UserEntity user = usersRepository.findById(userId).orElseThrow();
        // List<CardEntity> cards = cardsRepository.findAllByOwnerAndAlbumIsNull(user);
        // List<AlbumEntity> albums = albumsRepository.findAllByOwner(user);
        // for (int i = 0; i < cards.size(); i++) {
        // cards.get(i).setAlbum(albums.get(i % albums.size()));
        // }
        // cardsRepository.saveAll(cards);
        return cardsRepository.assignCardsToUserAlbums(userId)
                .stream()
                .map(c -> new Card(c.getId(), c.getOwner().getId(), Optional.of(c.getAlbum().getId()),
                        new Player(c.getPlayer().getName(), c.getPlayer().getJerseyNumber(),
                                c.getPlayer().getPosition(), c.getPlayer().getDateOfBirth())))
                .toList();

    }

    @Transactional
    public Optional<Card> transferCard(Integer cardId, Integer userId) {
        Integer count = cardsRepository.transferCard(cardId, userId);
        if (count == 0) {
            return Optional.empty();
        } else {
            CardEntity card = cardsRepository.findById(cardId).orElseThrow();
            return Optional.of(new Card(card.getId(), card.getOwner().getId(),
                    card.getAlbum() == null ? Optional.empty() : Optional.of(card.getAlbum().getId()),
                    new Player(card.getPlayer().getName(), card.getPlayer().getJerseyNumber(),
                            card.getPlayer().getPosition(), card.getPlayer().getDateOfBirth())));
        }
    }

    public List<Card> tradeAllCards(Integer userId1, Integer userId2) {
        Integer potentialUser1ToUser2 = cardsRepository.countMatchBetweenUsers(userId1, userId2);
        Integer potentialUser2ToUser1 = cardsRepository.countMatchBetweenUsers(userId2, userId1);
        Integer count = Math.min(potentialUser1ToUser2, potentialUser2ToUser1);
        if (count > 0) {
            ArrayList<CardEntity> result = new ArrayList<>(
                    cardsRepository.tradeCardsBetweenUsers(userId1, userId2, count));
            useAllCardAvailable(userId2);
            result.addAll(cardsRepository.tradeCardsBetweenUsers(userId2, userId1, count));
            useAllCardAvailable(userId1);
            return result.stream()
                    .map(c -> new Card(c.getId(), c.getOwner().getId(),
                            c.getAlbum() == null ? Optional.empty() : Optional.of(c.getAlbum().getId()),
                            new Player(c.getPlayer().getName(), c.getPlayer().getJerseyNumber(),
                                    c.getPlayer().getPosition(), c.getPlayer().getDateOfBirth())))
                    .toList();
        } else {
            return List.of();
        }
    }

    @Cacheable(value = "availablePlayers")
    private List<PlayerEntity> getAvailablePlayers() {
        return playersRepository.findAll();
    }

    public TradingUser getUserWithCardsAndAlbums(Integer userId) {
        UserEntity user = usersRepository.findByIdWithCardsAndAlbums(userId);
        return new TradingUser(new User(user.getId(), user.getUsername()),
                user.getOwnedCards()
                        .stream()
                        .map(c -> new Card(c.getId(), user.getId(),
                                c.getAlbum() == null ? Optional.empty() : Optional.of(c.getAlbum().getId()),
                                new Player(c.getPlayer().getName(), c.getPlayer().getJerseyNumber(),
                                        c.getPlayer().getPosition(), c.getPlayer().getDateOfBirth())))
                        .toList(),
                user.getOwnedAlbums()
                        .stream()
                        .map(a -> new Album(a.getId(), a.getTitle(), user.getId()))
                        .toList());

    }
}
